技术排错文档:Dify on Kubernetes (Fedora) 部署失败

技术排错文档:Dify on Kubernetes (Fedora) 部署失败

1. 概述 (Executive Summary)

本文档记录了一次在 Kubernetes (K8s) 集群上部署 Dify 应用(使用 Helm)的完整排错过程。最初的症状是 Dify 的核心 Pod(api, worker)无法启动,状态为 Pending

排错过程揭示了三个层层递进的根本问题:

  1. 存储模式冲突:Dify Helm Chart 默认请求 ReadWriteMany (RWX) 存储,而集群的默认 StorageClass (local-path) 只支持 ReadWriteOnce (RWO)。

  2. 系统资源耗尽:在部署 NFS 解决方案时,由于 Kubernetes kubelet 消耗了宿主机(Fedora 42)上所有的 inotify 资源,导致 systemd 无法管理 nfs-server 服务。

  3. Helm 配置覆盖失效:在修复了 NFS 问题后,错误的 values.yaml 文件导致 Helm 覆盖配置失败,安装时仍在使用错误的默认 StorageClass

本文档详细记录了每个问题的症状、诊断和最终解决方案。

2. 问题一:Dify Pod Pending (RWX vs. RWO 冲突)

2.1 症状

2.2 根本原因

Dify 的多个 Pod(如 apiworker)需要共享同一个存储卷来读写数据。因此,Helm Chart 为这个卷请求了 ReadWriteMany (RWX) 访问模式。

然而,集群的默认 StorageClass (local-path) 是一种本地路径存储,其数据存储在单一节点的磁盘上,天然无法被多个节点共享,因此它只支持 ReadWriteOnce (RWO)。

冲突点: 应用请求了 RWX,而存储只能提供 RWO。

2.3 解决方案

必须提供一个支持 ReadWriteMany (RWX) 的 StorageClass。我们选择在集群宿主机(r430 / Fedora 42)上搭建一个 NFS 服务器。

  1. 在 Fedora 主机上安装 NFS 服务

    sudo dnf install -y nfs-utils
    
  2. 创建并配置共享目录(我们使用了 /opt/nfs/kubedata

    sudo mkdir -p /opt/nfs/kubedata
    sudo chown -R nobody:nobody /opt/nfs/kubedata
    sudo chmod -R 777 /opt/nfs/kubedata
    
  3. 编辑 /etc/exports,添加配置(注意 no_root_squash 至关重要):

    /opt/nfs/kubedata    192.168.6.0/24(rw,sync,no_subtree_check,no_root_squash)
    
  4. 配置防火墙

    sudo firewall-cmd --permanent --add-service=nfs
    sudo firewall-cmd --permanent --add-service=rpc-bind
    sudo firewall-cmd --permanent --add-service=mountd
    sudo firewall-cmd --reload
    
  5. 启动 NFS 服务

    sudo systemctl enable --now nfs-server
    sudo systemctl enable --now rpcbind
    
  6. 在 Kubernetes 中部署 NFS Provisioner

    helm repo add nfs-subdir-external-provisioner [https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner/](https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner/)
    
    kubectl create namespace nfs-provisioner
    
    helm install nfs-provisioner nfs-subdir-external-provisioner/nfs-subdir-external-provisioner \
        --namespace nfs-provisioner \
        --set nfs.server=192.168.6.166 \
        --set nfs.path=/opt/nfs/kubedata \
        --set storageClass.name=nfs-client
    

3. 问题二:NFS Provisioner Pending (Inotify 资源耗尽)

3.1 症状

3.2 根本原因

这是一个**"宿主机"与"K8s节点"角色冲突**的经典问题。

  1. Kubernetes 的核心组件 kubelet 会创建海量的 inotify 监视器来跟踪 Cgroups、配置、卷挂载等。

  2. inotify 是 Linux 内核提供的功能,但它有资源上限max_user_watchesmax_user_instances)。

  3. kubelet 及其管理的容器耗尽了主机上所有的 inotify 资源配额。

  4. systemd(系统服务管理器)也需要 inotify 来监视和管理服务(如 nfs-server)。

  5. systemd 尝试重启 nfs-server 时,它无法分配新的 inotify 监视器,因此操作失败。

  6. nfs-server 服务无法正常运行,导致 kubelet 无法为 nfs-provisioner Pod 挂载 NFS 卷,Pod 因此卡住。

3.3 解决方案

必须提高 inotify 的内核限制,并重启主机以确保 systemd (PID 1) 能在 kubelet 之前获取这些新限制。

  1. 创建配置文件 /etc/sysctl.d/99-k8s-inotify.conf

    fs.inotify.max_user_watches=524288
    fs.inotify.max_user_instances=8192
    
  2. 加载配置(在重启前临时生效)

    sudo sysctl -p
    
  3. (关键) 重启主机 systemctl daemon-reexec 在此场景下已失效,因为 systemd 资源已耗尽。必须重启。

    sudo reboot
    
  4. 验证 重启后,nfs-provisioner Pod 自动变为 Running 状态。nfs-client StorageClass 准备就绪。

4. 问题三:Dify Install 仍使用 local-path (Helm values 覆盖失效)

4.1 症状

4.2 根本原因

Helm 只会覆盖与其官方 Chart 结构完全匹配的配置键。 我们自定义的 values.yaml 文件中的键(如 api:)与 Dify Helm Chart 的真正结构不符。Helm 在合并配置时,静默地忽略了这些不认识的键,并回退(Fallback)到使用 Chart 内部的默认值,即 storageClass: local-path

4.3 解决方案

必须获取并使用官方的 values.yaml 文件结构。

  1. 清理失败的部署

    helm uninstall dify -n dify
    kubectl delete pvc dify -n dify
    kubectl delete pvc dify-plugin-daemon -n dify
    
  2. (关键) 导出官方 values.yaml

    helm show values dify/dify > real-values.yaml
    
  3. 编辑 real-values.yaml 打开这个新文件,搜索 storageClass,找到所有需要 RWX 的地方(如 api, worker, pluginDaemon 的持久化配置),将其 storageClass 修改为 nfs-client

  4. 使用正确的文件安装

    helm install dify dify/dify \
      -f real-values.yaml \
      --namespace dify
    
  5. 验证 kubectl get pvc -n dify 显示新 PVC 的 STORAGECLASS 正确显示为 nfs-client,状态很快变为 Bound。所有 Pod 启动成功。